package com.itcast.activiti.service.impl;

import cn.itcast.ic.common.exception.ICException;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.itcast.activiti.cmd.*;
import com.itcast.activiti.constant.CategoryConstant;
import com.itcast.activiti.constant.ErrorInfo;
import com.itcast.activiti.constant.NodeKeyTag;
import com.itcast.activiti.domain.AddSign;
import com.itcast.activiti.domain.RemoveSign;
import com.itcast.activiti.entity.ApproveAddSign;
import com.itcast.activiti.entity.ApproveRemoveSign;
import com.itcast.activiti.service.IApproveAddSignService;
import com.itcast.activiti.service.IApproveRemoveSignService;
import com.itcast.activiti.service.IProcessControlService;
import com.itcast.activiti.utils.ActivityUtils;
import com.itcast.activiti.utils.UUIDUtils;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.engine.ManagementService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.impl.persistence.deploy.ProcessDefinitionCacheEntry;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.util.io.InputStreamSource;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class ProcessControlServiceImpl implements IProcessControlService {

    @Autowired
    private ProcessEngine processEngine;
    @Autowired
    private IApproveAddSignService approveAddSignService;
    @Autowired
    private IApproveRemoveSignService approveRemoveSignService;

    @Override
    public void addSign(String assignee, TaskEntity taskEntity, AddSign addSign) {
        String processDefinitionId = taskEntity.getProcessDefinitionId();
        String processInstanceId = taskEntity.getProcessInstanceId();
        String taskId = taskEntity.getId();
        Process process = this.getProcess(processDefinitionId);
        final String addSignId = UUIDUtils.randomUuid();
        final String targetNodeId = NodeKeyTag.USERTASK_PREFIX + addSignId;
        final String variableName = NodeKeyTag.USERS_PREFIX + addSignId;
        final String processId = NodeKeyTag.PROCESS_PREFIX + addSignId;
        String taskDefinitionKey = taskEntity.getTaskDefinitionKey();
        FlowElement flowElement = process.getFlowElement(taskDefinitionKey);
        UserTask userTask = (UserTask) flowElement;
        if (userTask.hasMultiInstanceLoopCharacteristics() && "BEFORE".equals(addSign.getType())) {
            throw new ICException(new ErrorInfo("同一审批节点有多人参与时，不支持前加签"));
        }
        UserTask task = ActivityUtils.buildUserTask(targetNodeId, "加签节点", variableName, addSign.getMultiApproveMode(), addSign.getUserEmails().size(), processEngine);
        addUserTask(process, task, taskDefinitionKey, addSign.getType());
        final String newProcessDefinitionId = deploy(addSign.getTenantId(), processDefinitionId, process, processId);
        approveAddSignService.add(addSign, addSignId, taskDefinitionKey, processDefinitionId, newProcessDefinitionId, assignee);
        ManagementService managementService = processEngine.getManagementService();
        updateInstanceAndHisProcessDefinitionId(processInstanceId, processDefinitionId, newProcessDefinitionId);
        HashMap<String, Object> variables = new HashMap<>();
        variables.put(variableName, addSign.getUserEmails());
        if ("BEFORE".equals(addSign.getType())) {
            managementService.executeCommand(new JumpCmd(taskId, targetNodeId, variables));
        } else {
            managementService.executeCommand(new AddExecutionVariables(taskEntity.getExecutionId(), variables));
        }
    }

    private void addUserTask(Process process, UserTask task, String taskDefinitionKey, String type) {
        Collection<FlowElement> flowElements = process.getFlowElements();
        if ("BEFORE".equals(type)) {
            for (FlowElement flowElement : flowElements) {
                if (flowElement instanceof SequenceFlow && ((SequenceFlow) flowElement).getTargetRef().equals(taskDefinitionKey)) {
                    SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
                    sequenceFlow.setTargetRef(task.getId());
                    sequenceFlow.setTargetFlowElement(task);
                }
            }
            process.addFlowElement(task);
            FlowElement flowElement = process.getFlowElement(taskDefinitionKey);
            SequenceFlow sequenceFlow = ActivityUtils.buildSequenceFlow(task.getId() + "To" + taskDefinitionKey,
                    task.getName() + "To" + flowElement.getName(), task.getId(), taskDefinitionKey);
            sequenceFlow.setTargetRef(taskDefinitionKey);
            sequenceFlow.setTargetFlowElement(process.getFlowElement(taskDefinitionKey));
            task.setOutgoingFlows(List.of(sequenceFlow));
            process.addFlowElement(sequenceFlow);
        } else {
            for (FlowElement flowElement : flowElements) {
                if (flowElement instanceof SequenceFlow && ((SequenceFlow) flowElement).getSourceRef().equals(taskDefinitionKey)) {
                    SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
                    sequenceFlow.setSourceRef(task.getId());
                    sequenceFlow.setSourceFlowElement(task);
                }
            }
            process.addFlowElement(task);
            SequenceFlow sequenceFlow = ActivityUtils.buildSequenceFlow(taskDefinitionKey + "To" + task.getId(),
                    task.getId() + "To" + taskDefinitionKey, taskDefinitionKey, task.getId());
            sequenceFlow.setSourceRef(taskDefinitionKey);
            sequenceFlow.setSourceFlowElement(process.getFlowElement(taskDefinitionKey));
            task.setOutgoingFlows(List.of(sequenceFlow));
            process.addFlowElement(sequenceFlow);
        }
    }

    /**
     * 更新实例运行相关信息和历史相关信息关联的流程定义id
     *
     * @param processInstanceId      流程实例id
     * @param processDefinitionId    流程定义id
     * @param newProcessDefinitionId 新流程定义id
     */
    private void updateInstanceAndHisProcessDefinitionId(String processInstanceId, String processDefinitionId, String newProcessDefinitionId) {
        List<HistoricActivityInstance> list = processEngine.getHistoryService().createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();
        if (!newProcessDefinitionId.equals(processDefinitionId)) {
            processEngine.getManagementService().executeCommand(new UpdateProcessDefinitionIdCmd(processInstanceId, newProcessDefinitionId));
            processEngine.getManagementService().executeCommand(new UpdateHistoricActivityCmd(processInstanceId, newProcessDefinitionId, list));
        }
    }

    @Override
    public void removeSign(String assignee, ProcessInstance processInstance, RemoveSign removeSign) {
        String processDefinitionId = processInstance.getProcessDefinitionId();
        String processInstanceId = processInstance.getProcessInstanceId();
        Process process = this.getProcess(processDefinitionId);
        // 删除节点逻辑
        Collection<FlowElement> flowElements = process.getFlowElements();
        String taskDefinitionKey = removeSign.getTaskDefinitionKey();
        List<SequenceFlow> sequenceFlows = new ArrayList<>();
        List<SequenceFlow> sourceSequenceFlows = new ArrayList<>();
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof SequenceFlow) {
                SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
                if (sequenceFlow.getTargetRef().equals(taskDefinitionKey)) {
                    sequenceFlows.add(sequenceFlow);
                }
                if (sequenceFlow.getSourceRef().equals(taskDefinitionKey)) {
                    sourceSequenceFlows.add(sequenceFlow);
                }
            }
        }
        if (sequenceFlows.size() > 1) {
            throw new RuntimeException("减签节点有两个或两个以上的来源节点！");
        }
        SequenceFlow sequenceFlow = sequenceFlows.get(0);
        FlowElement flowElement = process.getFlowElement(sequenceFlow.getSourceRef());
        for (SequenceFlow sourceSequenceFlow : sourceSequenceFlows) {
            sourceSequenceFlow.setSourceRef(flowElement.getId());
            sourceSequenceFlow.setSourceFlowElement(flowElement);
        }
        if (flowElement instanceof UserTask) {
            UserTask task = new UserTask();
            task.setOutgoingFlows(sourceSequenceFlows);
        }
        process.removeFlowElement(taskDefinitionKey);
        String removeSignId = UUIDUtils.randomUuid();
        String processId = NodeKeyTag.PROCESS_PREFIX + removeSignId;
        String newProcessDefinitionId = deploy(removeSign.getTenantId(), processDefinitionId, process, processId);
        approveRemoveSignService.add(removeSignId, taskDefinitionKey, processDefinitionId, newProcessDefinitionId, assignee);
        updateInstanceAndHisProcessDefinitionId(processInstanceId, processDefinitionId, newProcessDefinitionId);
    }

    @Override
    public List<String> findProcessDefinitionKeys(List<String> processDefinitionKeys) {
        ProcessDefinitionQuery processDefinitionQuery = processEngine.getRepositoryService().createProcessDefinitionQuery();
        List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKeys(new HashSet<>(processDefinitionKeys)).list();
        List<String> processDefinitionIds = list.stream().map(ProcessDefinition::getId).collect(Collectors.toList());
        List<ApproveAddSign> byProcDefIdOlds = approveAddSignService.findByProcDefIdOlds(processDefinitionIds);
        List<ApproveRemoveSign> byProcDefIdOlds1 = approveRemoveSignService.findByProcDefIdOlds(processDefinitionIds);
        processDefinitionKeys.addAll(byProcDefIdOlds.stream().map(v -> NodeKeyTag.PROCESS_PREFIX + v.getId()).collect(Collectors.toList()));
        processDefinitionKeys.addAll(byProcDefIdOlds1.stream().map(v -> NodeKeyTag.PROCESS_PREFIX + v.getId()).collect(Collectors.toList()));
        return processDefinitionKeys;
    }

    private String deploy(String tenantId, String processDefinitionId, Process process, String processId) {
        BpmnModel bpmnModel = new BpmnModel();
        bpmnModel.addProcess(process);
        BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
        byte[] convertToXML = bpmnXMLConverter.convertToXML(bpmnModel);
        String bytes = new String(convertToXML);
        System.out.println(bytes);
        if (isCopyProcess(processDefinitionId)) {
            ProcessDefinition processDefinition = processEngine.getRepositoryService().createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
            processEngine.getManagementService().executeCommand(new UpdateByteArrayEntityCmd(processDefinitionId, processDefinition.getDeploymentId(), convertToXML));
            return processDefinitionId;
        }
        process.setDocumentation(CategoryConstant.ADD_REMOVE_SIGN);
        process.setId(processId);
        Deployment deployment = processEngine.getRepositoryService().createDeployment()
                                             .addBpmnModel(processId + ".bpmn", bpmnModel)
                                             .name(processId)
                                             .key(processId)
                                             .category(CategoryConstant.ADD_REMOVE_SIGN)
                                             .tenantId(tenantId)
                                             .deploy();
        ProcessDefinition processDefinition = processEngine.getRepositoryService()
                                                           .createProcessDefinitionQuery()
                                                           .deploymentId(deployment.getId()).singleResult();
        return processDefinition.getId();
    }

    private boolean isCopyProcess(String processDefinitionId) {
        int addCount = approveAddSignService.count(Wrappers.<ApproveAddSign>lambdaQuery().eq(ApproveAddSign::getProcDefId, processDefinitionId));
        if (addCount > 0) {
            return true;
        }
        int removeCount = approveRemoveSignService.count(Wrappers.<ApproveRemoveSign>lambdaQuery().eq(ApproveRemoveSign::getProcDefId, processDefinitionId));
        return removeCount > 0;
    }

    private Process getProcess(String processDefinitionId) {
        ManagementService managementService = processEngine.getManagementService();
        managementService.executeCommand(new GetProcessCmd(processDefinitionId));
        ProcessDefinitionCacheEntry processDefinitionCacheEntry = managementService.executeCommand(new GetProcessDefinitionCacheEntryCmd(processDefinitionId, processEngine));
        Process process = processDefinitionCacheEntry.getProcess();
        if (isCopyProcess(processDefinitionId)) {
            return process;
        }
        BpmnModel bpmnModel = new BpmnModel();
        bpmnModel.addProcess(process);
        BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
        byte[] convertToXML = bpmnXMLConverter.convertToXML(bpmnModel);
        BpmnModel bpmnModel1 = bpmnXMLConverter.convertToBpmnModel(new InputStreamSource(new ByteArrayInputStream(convertToXML)), true, false, "UTF-8");
        return bpmnModel1.getMainProcess();
    }

}
